/*
 *  Created at 2011-01-09 15:59 GMT-03:00
 *  Author: Victor Williams Stafusa da Silva
 *  JSmartUnit
 */
package org.jsmartunit.model;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.jsmartunit.statement.DoNothing;

import org.junit.runner.Describable;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runners.model.Statement;

/**
 * @author Victor Williams Stafusa da Silva
 */
public final class TestRunner extends Statement implements Describable, Sortable, Filterable {

    private final Annotation[] annotations;
    private final RunnerDescriptions<TestRunner> order;
    private final List<TestRunner> children;
    private final String name;

    private Description description;
    private Statement statement;
    private Statement childrenRunnerStatement;
    private TestRunner parent;
    private Filter filter;
    private Sorter sorter;

    public TestRunner(String name, Annotation... annotations) {
        this(name, new DoNothing(), annotations);
    }

    public TestRunner(String name, Statement statement, Annotation... annotations) {
        if (statement == null) throw new IllegalArgumentException();
        this.annotations = annotations;
        this.name = name;
        this.parent = null;
        this.description = Description.createSuiteDescription(name, annotations);
        this.children = new LinkedList<TestRunner>();
        this.order = new RunnerDescriptions<TestRunner>(children);
        this.childrenRunnerStatement = new ChildrenRunner();
        this.statement = statement;
    }

    private class ChildrenRunner extends Statement {

        @Override
        public void evaluate() throws Throwable {
            evaluateChildren();
        }
    }

    public Statement getStatement() {
        return statement;
    }

    public void setStatement(Statement statement) {
        this.statement = statement;
    }

    public Statement getChildrenRunnerStatement() {
        return childrenRunnerStatement;
    }

    public void addChild(TestRunner child) {
        addChildren(Collections.singleton(child));
    }

    public void addChildren(Collection<TestRunner> newChildren) {
        for (TestRunner child : newChildren) {
            if (child.parent != null) throw new IllegalArgumentException();
        }
        for (TestRunner child : newChildren) {
            this.children.add(child);
            child.setParent(this);
            description.addChild(child.description);
        }
        order.update();
    }

    private void setParent(TestRunner parent) {
        this.parent = parent;
        String descName = parent == null ? name : (name + "(" + getRootRunner().name + ")");
        this.description = Description.createSuiteDescription(descName, annotations);
        for (TestRunner child : children) {
            child.setParent(this);
        }
    }

    private String getRootlessName() {
        if (parent == null || parent.parent == null) return name;
        return parent.getRootlessName() + name;
    }

    public Collection<TestRunner> getOrderedChildren() {
        return order.getFilteredElements();
    }

    @Override
    public void sort(Sorter sorter) {
        this.sorter = sorter;
        order.sort(sorter);
    }

    @Override
    public void filter(Filter filter) throws NoTestsRemainException {
        this.filter = filter;
        order.filter(filter);
    }

    public void filterSilently(Filter filter) {
        this.filter = filter;
        order.filterSilently(filter);
    }

    @Override
    public Description getDescription() {
        System.out.println("-- " + description.getDisplayName());
        return description;
    }

    @Override
    public void evaluate() throws Throwable {
        statement.evaluate();
    }

    public void evaluateChildren() throws Throwable {
        order.filter(filter);
        order.sort(sorter);
        for (TestRunner child : getOrderedChildren()) {
            child.filterSilently(filter);
            child.sort(sorter);
            child.evaluate();
        }
    }

    private TestRunner getRootRunner() {
        if (parent == null) return this;
        return parent.getRootRunner();
    }

    public TestRunner getParent() {
        return parent;
    }
}
